#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "../Comm/Tool.hpp"
#include "../Comm/Log.hpp"

namespace Runner_NS
{
    using namespace Tool_NS;
    using namespace Log_NS;

    class Runner
    {
    public:
        Runner() {}
        ~Runner() {}

    public:
        // 设置进程的CPU和内存资源上限
        static void SetProcLimit(int cpu_limit , int mem_limit)
        {
            // 设置CPU资源上限
            struct rlimit cpu_rlimit;
            cpu_rlimit.rlim_max = RLIM_INFINITY;
            cpu_rlimit.rlim_cur = cpu_limit;

            setrlimit(RLIMIT_CPU,&cpu_rlimit);

            // 设置内存资源上限
            struct rlimit mem_rlimit;
            mem_rlimit.rlim_max = RLIM_INFINITY;
            mem_rlimit.rlim_cur = mem_limit * 1024;  // KB --> B

            setrlimit(RLIMIT_AS,&mem_rlimit);
        }

        /***************************************************************************
         * Run的返回值有下面三种情况：
         * 1、返回值 > 0，程序运行异常，退出后收到了信号，返回值就是信号编号
         * 2、返回值 = 0，程序运行程序，程序运行后的结果已经放到了存放标准输出的临时文件中了
         * 3、返回值 < 0，程序发生了内部错误（临时文件打开失败、创建子进程失败等）
        ****************************************************************************/

        /***************************************************************************
         * Run的参数：
         * file_name：文件名，后缀和路径在Run中拼接
         * cpu_limit ：该程序运行时，可以使用的CPU资源上线
         * mem_limit ：该程序运行时，可以使用的内存资源上线（这里为了方便查看，以KB为单位）
        ****************************************************************************/

        static int Run(const std::string &file_name,int cpu_limit , int mem_limit)
        {
            /***************************************************************************
             * 程序运行后有三种情况：
             * 1、程序运行完毕，结果正确
             * 2、程序运行完毕，结果错误
             * 3、程序没跑完，程序异常了
             * 
             * 我们这里的Run不需要在意程序的结果是否正确，这是测试用例需要做的事
             * 这里的Run只需要在意程序是否运行完毕了
            ****************************************************************************/

            std::string execute_file = PathTool::Exe(file_name);   // 可执行程序
            std::string stdin_file = PathTool::Stdin(file_name);   // 重定向标准输入的临时文件
            std::string stdout_file = PathTool::Stdout(file_name); // 重定向标准输出的临时文件
            std::string stderr_file = PathTool::Stderr(file_name); // 重定向标准错误的临时文件

            /***************************************************************************
             * 通过文件名我们知道可执行程序是谁，在一个程序启动时会默认打开3个文件
             * 
             * 1、标准输入：用户输入的信息，这里暂时先不做处理
             * 2、标准输出：程序运行完毕，程序的输出结果
             * 3、标准错误：程序运行失败，运行时错误的信息
            ****************************************************************************/
            umask(0);
            int stdin_fd = open(stdin_file.c_str(), O_CREAT | O_RDONLY , 0644);
            int stdout_fd = open(stdout_file.c_str(), O_CREAT | O_WRONLY , 0644);
            int stderr_fd = open(stderr_file.c_str(), O_CREAT | O_WRONLY , 0644);

            if (stdin_fd < 0 || stdout_fd < 0 || stderr_fd < 0)
            {
                // 打开临时文件失败
                LOG(Error) << "打开标准文件失败" << std::endl;
                exit(-1);
            }

            pid_t pid = fork();

            if (pid < 0)
            {
                // 创建子进程失败，关闭文件描述符
                LOG(Error) << "创建子进程失败" << std::endl;
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd);

                exit(-2);
            }
            else if (pid == 0)
            {
                // 设置子进程的资源上限
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                // 这里一定要注意，不要设置成父进程的资源上限了！！！
                SetProcLimit(cpu_limit,mem_limit);

                // 子进程
                // 执行哪个文件，如何执行，不要忘记后面的nullptr
                dup2(stdin_fd,0);
                dup2(stdout_fd,1);
                dup2(stderr_fd,2);

                execl(execute_file.c_str(), execute_file.c_str(), nullptr);

                LOG(Error) << "程序替换失败，未能执行程序" << std::endl;
                // 子进程程序替换失败
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd);

                exit(-3);
            }
            else
            {
                // 父进程
                // 父进程不需要这些临时文件的文件描述符，关闭文件描述符
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd);

                int status = 0;
                waitpid(pid, &status, 0);

                LOG(Info) << "程序执行完毕,status : " << (status & 0x7F) << std::endl;
                // 获取信号
                return status & 0x7F;
            }
        }
    };
}
